package service

import (
	"encoding/hex"
	"encoding/json"

	"github.com/tokentransfer/node/core"
	"github.com/tokentransfer/node/core/pb"
	"github.com/tokentransfer/node/util"

	libcore "github.com/tokentransfer/interfaces/core"
	libcrypto "github.com/tokentransfer/interfaces/crypto"
)

type Message struct {
	Hash libcore.Hash

	Last   libcore.Hash
	Index  uint64
	Input  *Item
	Output *Item
	Time   int64 // in milliseconds
}

func (msg *Message) GetHash() libcore.Hash {
	return msg.Hash
}

func (msg *Message) SetHash(h libcore.Hash) {
	msg.Hash = h
}

func (msg *Message) UnmarshalBinary(d []byte) error {
	dt, o, err := core.UnmarshalData(d)
	if err != nil {
		return err
	}
	if dt != core.CORE_DATA_MAP {
		return util.ErrorOfInvalid("data map", "item")
	}
	im := o.(map[string]interface{})
	last, ok := im["last"]
	if ok && last != nil {
		msg.Last = last.([]byte)
	} else {
		msg.Last = nil
	}
	msg.Index = util.ToUint64(&im, "index")
	input, ok := im["input"]
	if ok && input != nil {
		inputBytes := input.([]byte)
		inputItem := &Item{}
		err := inputItem.UnmarshalBinary(inputBytes)
		if err != nil {
			return err
		}
		msg.Input = inputItem
	} else {
		msg.Input = nil
	}
	output, ok := im["output"]
	if ok && output != nil {
		outputBytes := output.([]byte)
		outputItem := &Item{}
		err := outputItem.UnmarshalBinary(outputBytes)
		if err != nil {
			return err
		}
		msg.Output = outputItem
	} else {
		msg.Output = nil
	}
	msg.Time = util.ToInt64(&im, "time")
	return nil
}

func (msg *Message) MarshalBinary() ([]byte, error) {
	inputBytes := []byte(nil)
	if msg.Input != nil {
		data, err := msg.Input.MarshalBinary()
		if err != nil {
			return nil, err
		}
		inputBytes = data
	}
	outputBytes := []byte(nil)
	if msg.Output != nil {
		data, err := msg.Output.MarshalBinary()
		if err != nil {
			return nil, err
		}
		outputBytes = data
	}
	im := map[string]interface{}{
		"last":   []byte(msg.Last),
		"index":  msg.Index,
		"input":  inputBytes,
		"output": outputBytes,
		"time":   msg.Time,
	}
	data, err := core.MarshalData(im)
	if err != nil {
		return nil, err
	}
	return data, nil
}

func (msg *Message) Raw(rt libcrypto.RawType) ([]byte, error) {
	return msg.MarshalBinary()
}

func (msg *Message) Json(format string) (interface{}, error) {
	msgJson, err := json.Marshal(msg)
	if err != nil {
		return nil, err
	}
	switch format {
	case "blob":
		msgData, err := msg.MarshalBinary()
		if err != nil {
			return nil, err
		}
		return hex.EncodeToString(msgData), nil
	case "data":
		m := map[string]interface{}{}
		err := json.Unmarshal(msgJson, &m)
		if err != nil {
			return nil, err
		}
		if msg.Input != nil {
			inputData, err := msg.Input.MarshalBinary()
			if err != nil {
				return nil, err
			}
			m["Input"] = hex.EncodeToString(inputData)
		}
		if msg.Output != nil {
			outputData, err := msg.Output.MarshalBinary()
			if err != nil {
				return nil, err
			}
			m["Output"] = hex.EncodeToString(outputData)
		}
		return m, nil
	default:
		return msg, nil
	}
}

type Item struct {
	Hash libcore.Hash

	SigningPubKey *libcore.PublicKey
	TxnSignature  *libcore.Signature

	Data libcore.Bytes
}

func (item *Item) GetHash() libcore.Hash {
	return item.Hash
}

func (item *Item) SetHash(h libcore.Hash) {
	item.Hash = h
}

func (item *Item) UnmarshalBinary(d []byte) error {
	_, msg, err := core.Unmarshal(d)
	if err != nil {
		return err
	}
	t := msg.(*pb.MessageKey)

	item.Data = t.GetMessageKey()

	pk := libcore.PublicKey(t.GetPublicKey())
	sig := libcore.Signature(t.GetSignature())

	item.SigningPubKey = &pk
	item.TxnSignature = &sig

	return nil
}

func (item *Item) MarshalBinary() ([]byte, error) {
	t := &pb.MessageKey{
		MessageKey: item.Data,
	}
	if item.SigningPubKey != nil {
		t.PublicKey = *item.SigningPubKey
	}
	if item.TxnSignature != nil {
		t.Signature = *item.TxnSignature
	}
	data, err := core.Marshal(t)
	if err != nil {
		return nil, err
	}
	return data, nil
}

func (item *Item) Raw(rt libcrypto.RawType) ([]byte, error) {
	if rt == libcrypto.RawIgnoreSigningFields {
		t := &pb.MessageKey{
			MessageKey: item.Data,
		}
		if item.SigningPubKey != nil {
			t.PublicKey = *item.SigningPubKey
		}
		data, err := core.Marshal(t)
		if err != nil {
			return nil, err
		}
		return data, nil
	}

	return item.MarshalBinary()
}

func (item *Item) GetAccount() libcore.Address {
	publicBytes := []byte(item.GetPublicKey())

	_, p, err := as.NewPublicFromBytes(publicBytes)
	if err != nil {
		return nil
	}
	a, err := p.GenerateAddress()
	if err != nil {
		return nil
	}
	return a
}

func (item *Item) GetPublicKey() libcore.PublicKey {
	if item.SigningPubKey != nil {
		return *item.SigningPubKey
	}
	return nil
}

func (item *Item) SetPublicKey(p libcore.PublicKey) {
	if p != nil {
		item.SigningPubKey = &p
	} else {
		item.SigningPubKey = nil
	}
}

func (item *Item) GetSignature() libcore.Signature {
	if item.TxnSignature != nil {
		return *item.TxnSignature
	}
	return nil
}

func (item *Item) SetSignature(s libcore.Signature) {
	if s != nil {
		item.TxnSignature = &s
	} else {
		item.TxnSignature = nil
	}
}
